package com.zyb.beiojcodesandbox.service;

import cn.hutool.core.date.StopWatch;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.dfa.WordTree;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.command.*;
import com.github.dockerjava.api.model.*;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.command.ExecStartResultCallback;
import com.zyb.beiojcodesandbox.model.ExecuteCodeRequest;
import com.zyb.beiojcodesandbox.model.ExecuteCodeResponse;
import com.zyb.beiojcodesandbox.model.ExecuteMessage;
import com.zyb.beiojcodesandbox.model.QuestionJudgeInfo;
import com.zyb.beiojcodesandbox.utils.ProcessUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author 十八岁讨厌编程
 * @date 2025/2/12 11:54
 * @PROJECT_NAME beioj-code-sandbox
 * @description
 */

@Slf4j
@Component

public class JavaDockerCodeSandbox extends JavaCodeSandboxTemplate {

    /**
     * docker创建的镜像名称
     */
    private static final String JAVA_IMAGE = "openjdk:8-alpine";


    public static void main(String[] args) {
        JavaDockerCodeSandbox javaDockerCodeSandbox = new JavaDockerCodeSandbox();
        ExecuteCodeRequest executeCodeRequest = new ExecuteCodeRequest();
        executeCodeRequest.setInputList(Arrays.asList("1 2", "1 12"));
        String code = ResourceUtil.readStr("testCode/simpleComputeArgs/Main.java", StandardCharsets.UTF_8);

        executeCodeRequest.setCode(code);
        executeCodeRequest.setLanguage("java");
        javaDockerCodeSandbox.executeCode(executeCodeRequest);
    }


    /**
     * 重写执行步骤，其他步骤Docker沙箱的实现内容不变
     *
     * @param inputList
     * @param userFile
     * @return
     * @throws IOException
     */
    @Override
    protected List<ExecuteMessage> runFile(List<String> inputList, File userFile) throws IOException {
        //创建容器
        //创建java环境的 docker容器
        // 获取默认的 Docker Client
        DockerClient dockerClient = DockerClientBuilder.getInstance().build();
        CreateContainerCmd containerCmd = dockerClient.createContainerCmd(JAVA_IMAGE);
        HostConfig hostConfig = new HostConfig();
        hostConfig.withMemory(100 * 1000 * 1000L);
        hostConfig.withMemorySwap(0L);
        hostConfig.withCpuCount(2L);
//        hostConfig.withSecurityOpts(Arrays.asList("seccomp=安全管理配置字符串"));
        //将用户的独立目录映射到容器中去
        hostConfig.setBinds(new Bind(userFile.getParent(), new Volume("/app")));
        CreateContainerResponse createContainerResponse = containerCmd
                .withHostConfig(hostConfig)
                .withNetworkDisabled(true)
                .withReadonlyRootfs(true)
                .withAttachStdin(true)
                .withAttachStderr(true)
                .withAttachStdout(true)
                .withTty(true)
                .exec();
        String containerId = createContainerResponse.getId();
        if (StrUtil.isNotBlank(containerId)) {
            log.info("docker容器创建成功");
        }else {
            log.info("docker容器创建失败");
        }

        //● 执行代码得到输出结果
        // 启动容器
        dockerClient.startContainerCmd(containerId).exec();

        // docker exec keen_blackwell java -cp /app Main 1 3
        // 执行命令并获取结果
        ArrayList<ExecuteMessage> outputMessages = new ArrayList<>();

        for (String inputStr : inputList) {
            String[] inputNum = inputStr.split(" ");
            String[] execCmd = ArrayUtil.append(new String[]{"java", "-cp", "/app", "Main"}, inputNum);
            log.info("docker容器执行的命令" + String.join(" ", execCmd));
            //首先创建执行的命令
            ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId)
                    .withCmd(execCmd)
                    .withAttachStderr(true)
                    .withAttachStdin(true)
                    .withAttachStdout(true)
                    .exec();

            String execId = execCreateCmdResponse.getId();
            //记录将要返回的数据
            final long[] memory = {0L};
            long time = 0L;
            final boolean[] timeout = {true};
            final String[] message = {null};
            final String[] errorMessage = {null};

            //执行监控指令
            StatsCmd statsCmd = dockerClient.statsCmd(containerId);
            statsCmd.exec(new ResultCallback<Statistics>() {
                @Override
                public void onStart(Closeable closeable) {
                    log.info("数据监控开启");
                }

                @Override
                public void onNext(Statistics statistics) {
                    Long usage = statistics.getMemoryStats().getUsage();
                    log.info("docker此次内存占用" + usage);
                    memory[0] = Math.max(usage,memory[0]);

                }

                @Override
                public void onError(Throwable throwable) {
                    log.info("数据监控出错" + throwable);
                }

                @Override
                public void onComplete() {
                    log.info("数据监控关闭");
                }

                @Override
                public void close() throws IOException {
                    log.info("数据监控关闭");
                }
            });

            //执行cmd命令（目的：判断是否超时、以及输出信息）
            try {
                StopWatch stopWatch = new StopWatch();
                stopWatch.start();
                dockerClient.execStartCmd(execId)
                        .exec(new ExecStartResultCallback() {

                            @Override
                            public void onNext(Frame frame) {
                                StreamType streamType = frame.getStreamType();
                                if (StreamType.STDERR.equals(streamType)) {
                                    errorMessage[0] = new String(frame.getPayload());
                                    log.info("docker沙箱输出错误结果：" + errorMessage[0]);
                                } else {
                                    message[0] = new String(frame.getPayload()).trim();
                                    log.info("docker沙箱输出结果：" + message[0]);
                                }
                                super.onNext(frame);
                            }

                            @Override
                            public void onComplete() {
                                timeout[0] = false;
                                log.info("本次执行未超时");
                                super.onComplete();
                            }

                        })
                        .awaitCompletion(TIME_OUT, TimeUnit.MILLISECONDS);
                stopWatch.stop();
                time = stopWatch.getLastTaskTimeMillis();
                statsCmd.close();
                //组装每次执行的结果
                ExecuteMessage executeMessage = new ExecuteMessage();
                executeMessage.setMessage(message[0]);
                executeMessage.setErrorMessage(errorMessage[0]);
                executeMessage.setTime(time);
                executeMessage.setMemory(memory[0]);
                outputMessages.add(executeMessage);
                log.info("该次docker执行返回结果：" + executeMessage);
            } catch (InterruptedException e) {
                log.info("程序执行异常");
                throw new RuntimeException(e);
            }

        }
        //强制删除docker容器
        dockerClient.removeContainerCmd(containerId)
                .withForce(true)
                .exec();
        log.info("删除容器成功");
        //关闭客户端
        dockerClient.close();

        log.info("沙箱返回:" + outputMessages);
        return outputMessages;
    }
}





